{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "initial_id",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T12:54:04.231124Z",
     "start_time": "2025-01-21T12:54:04.224124Z"
    },
    "collapsed": true,
    "execution": {
     "iopub.execute_input": "2025-01-21T15:06:09.640290Z",
     "iopub.status.busy": "2025-01-21T15:06:09.640169Z",
     "iopub.status.idle": "2025-01-21T15:06:14.532453Z",
     "shell.execute_reply": "2025-01-21T15:06:14.532004Z",
     "shell.execute_reply.started": "2025-01-21T15:06:09.640275Z"
    },
    "jupyter": {
     "outputs_hidden": true
    }
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/usr/local/lib/python3.10/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
      "  from .autonotebook import tqdm as notebook_tqdm\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "sys.version_info(major=3, minor=10, micro=14, releaselevel='final', serial=0)\n",
      "matplotlib 3.10.0\n",
      "numpy 1.26.4\n",
      "pandas 2.2.3\n",
      "sklearn 1.6.0\n",
      "torch 2.5.1+cu124\n",
      "cuda:0\n"
     ]
    }
   ],
   "source": [
    "import matplotlib as mpl\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "import numpy as np\n",
    "import sklearn\n",
    "import pandas as pd\n",
    "import os\n",
    "import sys\n",
    "import time\n",
    "from tqdm.auto import tqdm\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "\n",
    "print(sys.version_info)\n",
    "for module in mpl, np, pd, sklearn, torch:\n",
    "    print(module.__name__, module.__version__)\n",
    "\n",
    "device = torch.device(\"cuda:0\") if torch.cuda.is_available() else torch.device(\"cpu\")\n",
    "print(device)\n",
    "\n",
    "seed = 42"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "1dad802b6500ab83",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T12:54:05.910302Z",
     "start_time": "2025-01-21T12:54:04.240141Z"
    },
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T15:06:14.533814Z",
     "iopub.status.busy": "2025-01-21T15:06:14.533458Z",
     "iopub.status.idle": "2025-01-21T15:06:16.353443Z",
     "shell.execute_reply": "2025-01-21T15:06:16.352836Z",
     "shell.execute_reply.started": "2025-01-21T15:06:14.533795Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[(PosixPath('competitions/cifar-10/train/1.png'), 'frog'),\n",
      " (PosixPath('competitions/cifar-10/train/2.png'), 'truck'),\n",
      " (PosixPath('competitions/cifar-10/train/3.png'), 'truck'),\n",
      " (PosixPath('competitions/cifar-10/train/4.png'), 'deer'),\n",
      " (PosixPath('competitions/cifar-10/train/5.png'), 'automobile')]\n",
      "[(PosixPath('competitions/cifar-10/test/1.png'), 'cat'),\n",
      " (PosixPath('competitions/cifar-10/test/2.png'), 'cat'),\n",
      " (PosixPath('competitions/cifar-10/test/3.png'), 'cat'),\n",
      " (PosixPath('competitions/cifar-10/test/4.png'), 'cat'),\n",
      " (PosixPath('competitions/cifar-10/test/5.png'), 'cat')]\n",
      "50000 300000\n"
     ]
    }
   ],
   "source": [
    "from pathlib import Path\n",
    "\n",
    "# 目的：通过读取csv文件，得到图片的类别，并将图片路径和类别保存到DataFrame中。\n",
    "\n",
    "DATA_DIR1 = Path(\"./\")\n",
    "DATA_DIR2=Path(\"./competitions/cifar-10/\")\n",
    "\n",
    "train_labels_file = DATA_DIR1 / \"trainLabels.csv\"\n",
    "test_csv_file = DATA_DIR1 / \"sampleSubmission.csv\"  # 测试集模板csv文件\n",
    "train_folder = DATA_DIR2 / \"train/\"\n",
    "test_folder = DATA_DIR2 / \"test/\"\n",
    "\n",
    "# 所有的类别\n",
    "class_names = ['airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck']\n",
    "\n",
    "\n",
    "# filepath:csv文件路径，folder:图片所在文件夹\n",
    "def parse_csv_file(filepath, folder):\n",
    "    result = []\n",
    "    # 读取所有行\n",
    "    # with 语句：用于管理文件的上下文。在 with 块结束时，文件会自动关闭，无需手动调用 f.close()。\n",
    "    with open(filepath, 'r') as f:\n",
    "        # f.readlines()：读取文件的所有行，返回一个列表，每个元素是一行内容。\n",
    "        # 第一行不需要，因为第一行是标题\n",
    "        lines = f.readlines()[1:]\n",
    "    for line in lines:\n",
    "        # strip('\\n')：去除行末的换行符（\\n）。\n",
    "        # split(',')：按','分割字符串，返回一个列表。\n",
    "        image_id, label_str = line.strip('\\n').split(',')\n",
    "        # 得到图片的路径\n",
    "        image_full_path = folder / f\"{image_id}.png\"\n",
    "        # 得到对应图片的路径和分类\n",
    "        result.append((image_full_path, label_str))\n",
    "    return result\n",
    "\n",
    "\n",
    "# 得到训练集和测试集的图片路径和分类\n",
    "train_labels_info = parse_csv_file(train_labels_file, train_folder)\n",
    "test_csv_info = parse_csv_file(test_csv_file, test_folder)\n",
    "\n",
    "#打印\n",
    "import pprint\n",
    "\n",
    "pprint.pprint(train_labels_info[0:5])\n",
    "pprint.pprint(test_csv_info[0:5])\n",
    "print(len(train_labels_info), len(test_csv_info))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "8cd5063c39c678cf",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T12:54:05.981006Z",
     "start_time": "2025-01-21T12:54:05.911306Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T15:06:16.354212Z",
     "iopub.status.busy": "2025-01-21T15:06:16.353992Z",
     "iopub.status.idle": "2025-01-21T15:06:16.411994Z",
     "shell.execute_reply": "2025-01-21T15:06:16.411455Z",
     "shell.execute_reply.started": "2025-01-21T15:06:16.354196Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "                            filepath       class\n",
      "0  competitions/cifar-10/train/1.png        frog\n",
      "1  competitions/cifar-10/train/2.png       truck\n",
      "2  competitions/cifar-10/train/3.png       truck\n",
      "3  competitions/cifar-10/train/4.png        deer\n",
      "4  competitions/cifar-10/train/5.png  automobile\n",
      "                                filepath       class\n",
      "0  competitions/cifar-10/train/45001.png       horse\n",
      "1  competitions/cifar-10/train/45002.png  automobile\n",
      "2  competitions/cifar-10/train/45003.png        deer\n",
      "3  competitions/cifar-10/train/45004.png  automobile\n",
      "4  competitions/cifar-10/train/45005.png    airplane\n",
      "                           filepath class\n",
      "0  competitions/cifar-10/test/1.png   cat\n",
      "1  competitions/cifar-10/test/2.png   cat\n",
      "2  competitions/cifar-10/test/3.png   cat\n",
      "3  competitions/cifar-10/test/4.png   cat\n",
      "4  competitions/cifar-10/test/5.png   cat\n"
     ]
    }
   ],
   "source": [
    "# 取前45000张图片作为训练集\n",
    "train_df = pd.DataFrame(train_labels_info[0:45000])\n",
    "# 取后5000张图片作为验证集\n",
    "valid_df = pd.DataFrame(train_labels_info[45000:])\n",
    "# 测试集\n",
    "test_df = pd.DataFrame(test_csv_info)\n",
    "\n",
    "# 为 Pandas DataFrame 的列重新命名\n",
    "train_df.columns = ['filepath', 'class']\n",
    "valid_df.columns = ['filepath', 'class']\n",
    "test_df.columns = ['filepath', 'class']\n",
    "\n",
    "print(train_df.head())\n",
    "print(valid_df.head())\n",
    "print(test_df.head())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "af9444393c2debca",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T12:54:05.989023Z",
     "start_time": "2025-01-21T12:54:05.982013Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T15:06:16.412989Z",
     "iopub.status.busy": "2025-01-21T15:06:16.412570Z",
     "iopub.status.idle": "2025-01-21T15:06:18.427102Z",
     "shell.execute_reply": "2025-01-21T15:06:18.426633Z",
     "shell.execute_reply.started": "2025-01-21T15:06:16.412971Z"
    }
   },
   "outputs": [],
   "source": [
    "from PIL import Image\n",
    "from torch.utils.data import Dataset, DataLoader\n",
    "from torchvision import transforms\n",
    "\n",
    "\n",
    "class Cifar10Dataset(Dataset):\n",
    "    df_map = {\n",
    "        \"train\": train_df,\n",
    "        \"eval\": valid_df,\n",
    "        \"test\": test_df\n",
    "    }\n",
    "    # enumerate(class_names)：遍历 class_names，返回索引和类别名称的元组\n",
    "    # 将类别名称映射为索引，通常用于将标签转换为模型可以处理的数值。\n",
    "    label_to_idx = {\n",
    "        label: idx for idx, label in enumerate(class_names)\n",
    "    }\n",
    "    # 将索引映射为类别名称，通常用于将模型的输出（索引）转换回可读的类别名称\n",
    "    idx_to_label = {\n",
    "        idx: label for idx, label in enumerate(class_names)\n",
    "    }\n",
    "\n",
    "    def __init__(self, mode, transform=None):\n",
    "        # 获取对应模式的df，不同字符串对应不同模式\n",
    "        self.df = self.df_map.get(mode, None)\n",
    "        if self.df is None:\n",
    "            raise ValueError(f\"Invalid mode: {mode}\")\n",
    "        self.transform = transform\n",
    "\n",
    "    def __getitem__(self, index):\n",
    "        # 获取图片路径和标签\n",
    "        img_path, label = self.df.iloc[index]\n",
    "        # 打开一张图片并将其转换为 RGB 格式\n",
    "        img = Image.open(img_path).convert('RGB')  # 确保图片具有三个通道\n",
    "        # 应用数据增强\n",
    "        img = self.transform(img)\n",
    "        # label 转换为 idx\n",
    "        label = self.label_to_idx[label]\n",
    "        return img, label\n",
    "\n",
    "    def __len__(self):\n",
    "        # 返回df的行数,样本数\n",
    "        return self.df.shape[0]\n",
    "\n",
    "\n",
    "IMAGE_SIZE = 32\n",
    "mean, std = [0.4914, 0.4822, 0.4465], [0.247, 0.243, 0.261]\n",
    "\n",
    "#数据增强\n",
    "# ToTensor还将图像的维度从[height, width, channels]转换为[channels, height, width]。\n",
    "transforms_train = transforms.Compose([\n",
    "    transforms.Resize((IMAGE_SIZE, IMAGE_SIZE)),  # 缩放\n",
    "    transforms.RandomRotation(40),  # 随机旋转\n",
    "    transforms.RandomHorizontalFlip(),  # 随机水平翻转\n",
    "    transforms.ToTensor(),  # 转换为Tensor\n",
    "    transforms.Normalize(mean, std)  # 标准化\n",
    "])\n",
    "\n",
    "transforms_eval = transforms.Compose([\n",
    "    transforms.Resize((IMAGE_SIZE, IMAGE_SIZE)),\n",
    "    transforms.ToTensor(),\n",
    "    transforms.Normalize(mean, std)\n",
    "])\n",
    "\n",
    "train_ds = Cifar10Dataset(mode=\"train\", transform=transforms_train)\n",
    "eval_ds = Cifar10Dataset(mode=\"eval\", transform=transforms_eval)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "d17ef4a56f0b6ae1",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T12:54:05.996484Z",
     "start_time": "2025-01-21T12:54:05.991037Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T15:06:18.427854Z",
     "iopub.status.busy": "2025-01-21T15:06:18.427603Z",
     "iopub.status.idle": "2025-01-21T15:06:18.492968Z",
     "shell.execute_reply": "2025-01-21T15:06:18.492574Z",
     "shell.execute_reply.started": "2025-01-21T15:06:18.427836Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([3, 32, 32])"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "train_ds[0][0].shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "161282a4e612afcd",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T12:54:06.001586Z",
     "start_time": "2025-01-21T12:54:05.997489Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T15:06:18.493563Z",
     "iopub.status.busy": "2025-01-21T15:06:18.493411Z",
     "iopub.status.idle": "2025-01-21T15:06:18.496398Z",
     "shell.execute_reply": "2025-01-21T15:06:18.495919Z",
     "shell.execute_reply.started": "2025-01-21T15:06:18.493547Z"
    }
   },
   "outputs": [],
   "source": [
    "batch_size = 64\n",
    "\n",
    "train_loader = DataLoader(train_ds, batch_size=batch_size,\n",
    "                          shuffle=True)\n",
    "eval_loader = DataLoader(eval_ds, batch_size=batch_size,\n",
    "                         shuffle=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "20b5b31416450980",
   "metadata": {},
   "source": [
    "## 模型定义"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "b73daeeb83b4b527",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T12:54:06.011557Z",
     "start_time": "2025-01-21T12:54:06.002589Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T15:06:18.498053Z",
     "iopub.status.busy": "2025-01-21T15:06:18.497696Z",
     "iopub.status.idle": "2025-01-21T15:06:18.506942Z",
     "shell.execute_reply": "2025-01-21T15:06:18.506525Z",
     "shell.execute_reply.started": "2025-01-21T15:06:18.498035Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "             model.0.weight             paramerters num: 864\n",
      "              model.0.bias              paramerters num: 32\n",
      "             model.2.weight             paramerters num: 9216\n",
      "              model.2.bias              paramerters num: 32\n",
      "             model.5.weight             paramerters num: 9216\n",
      "              model.5.bias              paramerters num: 32\n",
      "             model.7.weight             paramerters num: 9216\n",
      "              model.7.bias              paramerters num: 32\n",
      "            model.10.weight             paramerters num: 9216\n",
      "             model.10.bias              paramerters num: 32\n",
      "            model.12.weight             paramerters num: 9216\n",
      "             model.12.bias              paramerters num: 32\n",
      "            model.16.weight             paramerters num: 5120\n",
      "             model.16.bias              paramerters num: 10\n"
     ]
    }
   ],
   "source": [
    "class VGG(nn.Module):\n",
    "    def __init__(self, num_classes):\n",
    "        super().__init__()\n",
    "        self.model = nn.Sequential(\n",
    "            nn.Conv2d(in_channels=3, out_channels=32, kernel_size=3, padding=\"same\"),  # 32*32*32\n",
    "            nn.ReLU(),\n",
    "            nn.Conv2d(in_channels=32, out_channels=32, kernel_size=3, padding=\"same\"),  # 32*32*32\n",
    "            nn.ReLU(),\n",
    "            nn.MaxPool2d(kernel_size=2),  # 32*16*16\n",
    "            nn.Conv2d(in_channels=32, out_channels=32, kernel_size=3, padding=\"same\"),  # 32*16*16\n",
    "            nn.ReLU(),\n",
    "            nn.Conv2d(in_channels=32, out_channels=32, kernel_size=3, padding=\"same\"),  # 32*16*16\n",
    "            nn.ReLU(),\n",
    "            nn.MaxPool2d(kernel_size=2),  # 32*8*8\n",
    "            nn.Conv2d(in_channels=32, out_channels=32, kernel_size=3, padding=\"same\"),  # 32*8*8\n",
    "            nn.ReLU(),\n",
    "            nn.Conv2d(in_channels=32, out_channels=32, kernel_size=3, padding=\"same\"),  # 32*8*8\n",
    "            nn.ReLU(),\n",
    "            nn.MaxPool2d(kernel_size=2),  # 32*4*4\n",
    "            nn.Flatten(),\n",
    "            nn.Linear(512, num_classes),\n",
    "        )\n",
    "        self.init_weights()\n",
    "\n",
    "    def init_weights(self):\n",
    "        \"\"\"\n",
    "        使用 xavier 均匀分布来初始化全连接层、卷积层的权重 W\n",
    "        \"\"\"\n",
    "        for m in self.modules():\n",
    "            if isinstance(m, (nn.Linear, nn.Conv2d)):\n",
    "                nn.init.xavier_uniform_(m.weight)\n",
    "                nn.init.zeros_(m.bias)\n",
    "                \n",
    "    def forward(self, x):\n",
    "        return self.model(x)\n",
    "\n",
    "\n",
    "for key, value in VGG(len(class_names)).named_parameters():\n",
    "    print(f\"{key:^40}paramerters num: {np.prod(value.shape)}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "b294d25e7f6287d0",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T12:54:06.018461Z",
     "start_time": "2025-01-21T12:54:06.012559Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T15:06:18.507775Z",
     "iopub.status.busy": "2025-01-21T15:06:18.507400Z",
     "iopub.status.idle": "2025-01-21T15:06:18.512022Z",
     "shell.execute_reply": "2025-01-21T15:06:18.511610Z",
     "shell.execute_reply.started": "2025-01-21T15:06:18.507740Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Total trainable parameters: 52266\n"
     ]
    }
   ],
   "source": [
    "total_params = sum(p.numel() for p in VGG(len(class_names)).parameters() if p.requires_grad)\n",
    "print(f\"Total trainable parameters: {total_params}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "76ca49d0dca74376",
   "metadata": {},
   "source": [
    "## 模型训练"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "632d31ce33563b0c",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T12:54:06.080978Z",
     "start_time": "2025-01-21T12:54:06.019464Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T15:06:18.512823Z",
     "iopub.status.busy": "2025-01-21T15:06:18.512472Z",
     "iopub.status.idle": "2025-01-21T15:06:18.604502Z",
     "shell.execute_reply": "2025-01-21T15:06:18.604037Z",
     "shell.execute_reply.started": "2025-01-21T15:06:18.512807Z"
    }
   },
   "outputs": [],
   "source": [
    "from sklearn.metrics import accuracy_score\n",
    "\n",
    "\n",
    "@torch.no_grad()  # 装饰器，禁止梯度计算\n",
    "def evaluate(model, data_loader, loss_fct):\n",
    "    loss_list = []\n",
    "    pred_list = []\n",
    "    label_list = []\n",
    "    for datas, labels in data_loader:\n",
    "        datas = datas.to(device)\n",
    "        labels = labels.to(device)\n",
    "\n",
    "        # 前向传播\n",
    "        logits = model(datas)\n",
    "        loss = loss_fct(logits, labels)  # 验证集损失\n",
    "        # tensor.item() 获取tensor的数值，loss是只有一个元素的tensor\n",
    "        loss_list.append(loss.item())\n",
    "\n",
    "        # 预测\n",
    "        preds = logits.argmax(axis=-1)  # 预测类别\n",
    "        pred_list.extend(preds.cpu().numpy().tolist())  # tensor转numpy，再转list\n",
    "        label_list.extend(labels.cpu().numpy().tolist())\n",
    "\n",
    "    acc = accuracy_score(label_list, pred_list)  # 计算准确率\n",
    "    return np.mean(loss_list), acc  # # 返回验证集平均损失和准确率"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "28c2ba49f4ff546",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T12:54:06.088016Z",
     "start_time": "2025-01-21T12:54:06.082983Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T15:06:18.605330Z",
     "iopub.status.busy": "2025-01-21T15:06:18.605033Z",
     "iopub.status.idle": "2025-01-21T15:06:18.609734Z",
     "shell.execute_reply": "2025-01-21T15:06:18.609292Z",
     "shell.execute_reply.started": "2025-01-21T15:06:18.605313Z"
    }
   },
   "outputs": [],
   "source": [
    "class SaveCheckpointsCallback:\n",
    "    def __init__(self, save_dir, save_step=500, save_best_only=True):\n",
    "        self.save_dir = save_dir  # 保存路径\n",
    "        self.save_step = save_step  # 保存步数\n",
    "        self.save_best_only = save_best_only  # 是否只保存最好的模型\n",
    "        self.best_metric = -1  # 最好的指标，指标不可能为负数，所以初始化为-1\n",
    "        # 创建保存路径\n",
    "        if not os.path.exists(self.save_dir):  # 如果不存在保存路径，则创建\n",
    "            os.makedirs(self.save_dir)\n",
    "\n",
    "    # 对象被调用时：当你将对象像函数一样调用时，Python 会自动调用 __call__ 方法。\n",
    "    # state_dict() 返回模型参数的字典，包括模型参数和优化器参数\n",
    "    # metric 是指标，可以是验证集的准确率，也可以是其他指标\n",
    "    def __call__(self, step, state_dict, metric=None):\n",
    "        if step % self.save_step > 0:\n",
    "            return  # 不是保存步数，则直接返回\n",
    "\n",
    "        if self.save_best_only:\n",
    "            assert metric is not None  # 必须传入metric\n",
    "            if metric >= self.best_metric:  # 如果当前指标大于最好的指标\n",
    "                # save checkpoint\n",
    "                # 保存最好的模型，覆盖之前的模型，不保存step，只保存state_dict，即模型参数，不保存优化器参数\n",
    "                torch.save(state_dict, os.path.join(self.save_dir, \"07_VGG.ckpt\"))\n",
    "                self.best_metric = metric  # 更新最好的指标\n",
    "        else:\n",
    "            # 保存模型\n",
    "            torch.save(state_dict, os.path.join(self.save_dir, f\"{step}.ckpt\"))\n",
    "            # 保存每个step的模型，不覆盖之前的模型，保存step，保存state_dict，即模型参数，不保存优化器参数\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "cd4d61586ad24eb5",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T12:54:06.095070Z",
     "start_time": "2025-01-21T12:54:06.090023Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T15:06:18.610562Z",
     "iopub.status.busy": "2025-01-21T15:06:18.610245Z",
     "iopub.status.idle": "2025-01-21T15:06:18.614015Z",
     "shell.execute_reply": "2025-01-21T15:06:18.613574Z",
     "shell.execute_reply.started": "2025-01-21T15:06:18.610547Z"
    }
   },
   "outputs": [],
   "source": [
    "class EarlyStopCallback:\n",
    "    def __init__(self, patience=5, min_delta=0.01):\n",
    "        self.patience = patience  # 多少个step没有提升就停止训练\n",
    "        self.min_delta = min_delta  # 最小的提升幅度\n",
    "        self.best_metric = -1  # 记录的最好的指标\n",
    "        self.counter = 0  # 计数器，记录连续多少个step没有提升\n",
    "\n",
    "    def __call__(self, metric):\n",
    "        if metric >= self.best_metric + self.min_delta:  # 如果指标提升了\n",
    "            self.best_metric = metric  # 更新最好的指标\n",
    "            self.counter = 0  # 计数器清零\n",
    "        else:\n",
    "            self.counter += 1  # 计数器加一\n",
    "\n",
    "    @property  # 使用@property装饰器，使得 对象.early_stop可以调用，不需要()\n",
    "    def early_stop(self):\n",
    "        # 如果计数器大于等于patience，则返回True，停止训练\n",
    "        return self.counter >= self.patience"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "725cf8a521379801",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T12:54:06.102952Z",
     "start_time": "2025-01-21T12:54:06.096076Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T15:06:18.614729Z",
     "iopub.status.busy": "2025-01-21T15:06:18.614533Z",
     "iopub.status.idle": "2025-01-21T15:06:18.621121Z",
     "shell.execute_reply": "2025-01-21T15:06:18.620685Z",
     "shell.execute_reply.started": "2025-01-21T15:06:18.614715Z"
    }
   },
   "outputs": [],
   "source": [
    "def training(model,\n",
    "             train_loader,\n",
    "             val_loader,\n",
    "             epoch,\n",
    "             loss_fct,\n",
    "             optimizer,\n",
    "             save_ckpt_callback=None,\n",
    "             early_stop_callback=None,\n",
    "             eval_step=500,\n",
    "             ):\n",
    "    record_dict = {\n",
    "        \"train\": [],\n",
    "        \"val\": []\n",
    "    }\n",
    "\n",
    "    global_step = 0  # 全局步数\n",
    "    model.train()  # 训练模式\n",
    "    with tqdm(total=epoch * len(train_loader)) as pbar:\n",
    "        for epoch_id in range(epoch):\n",
    "            for datas, labels in train_loader:\n",
    "                datas = datas.to(device)\n",
    "                labels = labels.to(device)\n",
    "\n",
    "                # 前向传播\n",
    "                logits = model(datas)\n",
    "                loss = loss_fct(logits, labels)  # 训练集损失\n",
    "                preds = logits.argmax(axis=-1)  # 预测类别\n",
    "\n",
    "                # 反向传播\n",
    "                optimizer.zero_grad()  # 梯度清零\n",
    "                loss.backward()  # 反向传播\n",
    "                optimizer.step()  # 优化器更新参数\n",
    "\n",
    "                # 计算准确率\n",
    "                acc = accuracy_score(labels.cpu().numpy(), preds.cpu().numpy())\n",
    "                loss = loss.cpu().item()\n",
    "\n",
    "                record_dict[\"train\"].append({\n",
    "                    \"loss\": loss,\n",
    "                    \"acc\": acc,\n",
    "                    \"step\": global_step\n",
    "                })\n",
    "\n",
    "                # 评估\n",
    "                if global_step % eval_step == 0:\n",
    "                    model.eval()  # 评估模式\n",
    "                    # 验证集损失和准确率\n",
    "                    val_loss, val_acc = evaluate(model, val_loader, loss_fct)\n",
    "                    record_dict[\"val\"].append({\n",
    "                        \"loss\": val_loss,\n",
    "                        \"acc\": val_acc,\n",
    "                        \"step\": global_step\n",
    "                    })\n",
    "                    model.train()  # 训练模式\n",
    "\n",
    "                    # 2. 保存模型权重 save model checkpoint\n",
    "                    if save_ckpt_callback is not None:\n",
    "                        # model.state_dict() 返回模型参数的字典，包括模型参数和优化器参数\n",
    "                        save_ckpt_callback(global_step, model.state_dict(), val_acc)\n",
    "                        # 保存最好的模型，覆盖之前的模型，保存step，保存state_dict,通过metric判断是否保存最好的模型\n",
    "\n",
    "                    # 3. 早停 early stopping\n",
    "                    if early_stop_callback is not None:\n",
    "                        # 验证集准确率不再提升，则停止训练\n",
    "                        early_stop_callback(val_acc)\n",
    "                        # 验证集准确率不再提升，则停止训练\n",
    "                        if early_stop_callback.early_stop:\n",
    "                            print(f\"Early stop at epoch {epoch_id} / global_step {global_step}\")\n",
    "                            return record_dict  # 早停，返回记录字典 record_dict\n",
    "\n",
    "                # 更新进度条和全局步数\n",
    "                pbar.update(1)  # 更新进度条\n",
    "                global_step += 1  # 全局步数加一\n",
    "                pbar.set_postfix({\"epoch\": epoch_id})\n",
    "\n",
    "    return record_dict  # 训练结束，返回记录字典 record_dict\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "22bf1e82c9715088",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T12:54:06.110274Z",
     "start_time": "2025-01-21T12:54:06.103946Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T15:06:18.621863Z",
     "iopub.status.busy": "2025-01-21T15:06:18.621627Z",
     "iopub.status.idle": "2025-01-21T15:06:18.626876Z",
     "shell.execute_reply": "2025-01-21T15:06:18.626493Z",
     "shell.execute_reply.started": "2025-01-21T15:06:18.621848Z"
    }
   },
   "outputs": [],
   "source": [
    "epoch = 100\n",
    "\n",
    "model = VGG(len(class_names))  # 定义模型\n",
    "\n",
    "# 1. 定义损失函数 采用MSE损失\n",
    "loss_fct = nn.CrossEntropyLoss()\n",
    "\n",
    "# 2. 定义优化器 采用 adam\n",
    "optimizer = torch.optim.Adam(model.parameters(), lr=0.001)\n",
    "\n",
    "# 3.save model checkpoint\n",
    "if not os.path.exists(\"checkpoints\"):\n",
    "    os.makedirs(\"checkpoints\")\n",
    "save_ckpt_callback = SaveCheckpointsCallback(save_dir=\"checkpoints\", save_step=len(train_loader), save_best_only=True)\n",
    "\n",
    "# 4. early stopping\n",
    "early_stop_callback = EarlyStopCallback(patience=5, min_delta=0.01)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "5335f62fe5e4977f",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T12:54:20.937545Z",
     "start_time": "2025-01-21T12:54:06.111276Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T15:06:18.627394Z",
     "iopub.status.busy": "2025-01-21T15:06:18.627262Z",
     "iopub.status.idle": "2025-01-21T15:14:12.358213Z",
     "shell.execute_reply": "2025-01-21T15:14:12.357769Z",
     "shell.execute_reply.started": "2025-01-21T15:06:18.627381Z"
    }
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      " 19%|█▉        | 13376/70400 [07:53<33:39, 28.24it/s, epoch=18]  "
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Early stop at epoch 19 / global_step 13376\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    }
   ],
   "source": [
    "model = model.to(device)  # 将模型移到GPU上\n",
    "\n",
    "# 训练过程\n",
    "record_dict = training(\n",
    "    model,\n",
    "    train_loader,\n",
    "    eval_loader,\n",
    "    epoch,\n",
    "    loss_fct,\n",
    "    optimizer,\n",
    "    save_ckpt_callback=save_ckpt_callback,\n",
    "    early_stop_callback=early_stop_callback,\n",
    "    eval_step=len(train_loader)\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "1a790e5f4635d5e8",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T12:54:20.938547Z",
     "start_time": "2025-01-21T12:54:20.938547Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T15:14:12.359029Z",
     "iopub.status.busy": "2025-01-21T15:14:12.358733Z",
     "iopub.status.idle": "2025-01-21T15:14:12.524451Z",
     "shell.execute_reply": "2025-01-21T15:14:12.523878Z",
     "shell.execute_reply.started": "2025-01-21T15:14:12.359012Z"
    }
   },
   "outputs": [
    {
     "data": {
      "image/png": "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eSUzZ84kNTW11n3eddddTJo0qdbnV61axSOPPMLvfvc7Dhw4wP3338/cuXPdbfj000/517/+xX//+1+OHTvG559/zuDBgwHYtWsXDz/8ME899RRHjhxh3bp1XHLJJQ36XYUQoqEURWFvWgEARp16ieh63FSu+XP6x4Wg03q/EEFNZo9O4MaRXXAo8PCHe8kobFsX6b5mqbBzKke9xndVydtwONOXTfKY3pOV161bV+XxsmXLiI6OZvfu3bV+6H7wwQdVHv/vf//j008/ZePGjcyZM8fD5jZemS4EbKA1S6AjRLOymeFvnb2yKy0Q5skGfz4PxsB6VwsPD2f69OksX76cK664AoBPPvmEyMhILrvsMrRaLUOHDnWv//TTT7Nq1SpWr17N/Pnza9xnXFxctR6iyp5//nnuuusuHnroIQAWLFjADz/8wPPPP89ll11GamoqsbGxTJ48GYPBQNeuXbnooosASE1NJTAwkKuvvprg4GC6devG8OHDG/yyCCFEQ5zONVNgtmHSa5k6MJYv951nb2oBl/WNbvK+D55TCxE0d9paZRqNhqevHcSBc4WkZBQzf/kePvz1xRh0Ho7ccDgg9xgYgyCo6a9FW3Eqp5QKh0Kwn55bLurKc98cIelQJreN6ebrpjWYR4HOLxUWqn+0ERERDd7GbDZjs9nq3MZisWCxXJjvpqhI/RbAZrNhs3lex9tms2ExBEM5GMpzGrUP0Xq5zqec15Zns9lQFAWHw3HhIt/h8NngP4fDoX4gNcAtt9zC/fffz8svv4zJZOKDDz5g9uzZgPqes3jxYtauXUt6ejoVFRWUlZVx5syZKsGM63cHeOaZZy60oYafhw8f5t57762y/bhx43jppZdwOBzccMMNvPDCC/To0YNp06Yxffp0Zs6ciV6v54orrqBbt27u56ZNm8Z1111HQEBAE18x3G1UFAWbzYZOp6vynPxfCQHnC8r47ngON4zograFeiN8Yc8ZNW1tcHwoF3WPcAY63hmnc7AFChHUxN+o47XbRzLz39vZdSafv3+dwuNXD6h/Q4cdUnfAoS/g0GoocQ3E16APjORShz+6onchtDMExUJwLATHQXCM+jMwGnRNusz2uSMZatpan5hgpg6I4blvjvD98VxKLBUEmdrG79boVjocDh599FHGjx/PoEGDGrzdH//4Rzp37szkyZNrXWfJ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806t97/++uv8/e9/57333mPz5s14e3szfPhwysvLm7inQjSenJtcm5ybXIecm1yPSwU68+bNY9q0aUyZMoXu3bvz3nvv4eXlxcKFC2vdfs6cOYSHh7N79+4m7qloCDc3N8LCwmyXkJCQOreVY3tlGzFiBC+99BJjx4696D5VVZk/fz7PPvsso0ePJi4ujsWLF3Pq1Kk6U0CqqqqYOnUqXbt2JS0tzcm9F8I+cm5ybXJuch1ybnI9LhPomEwmtm/fzpAhQ2y36XQ6hgwZwq+//lpjW1VVeeyxx1i8eDEbNmwgLi6uqbsrGuDIkSNERETQvn17JkyYUOuHhhzblu/48eNkZWXVeC/7+/vTv3//i97LABUVFdx9990kJyezYcMG2rRp05TdFeKS5Nzk+uTcdHWQc1PL5NbcHXCUvLw8qqqqCA0NrXF7aGgoBw8etF2vrKzk/vvvZ+fOnfzyyy9ERkY2dVdFA/Tv359PPvmELl26kJmZyfPPP8/111/P3r178fX1BeTYuoqsrCyAWt/L1vusiouLufXWW6moqGDt2rX4+/s3WT+FqA85N7k2OTddPeTc1DK5TKBTX0888QRGo5HffvvtksPL4soyYsQI2//j4uLo378/bdu25auvvuLBBx8E5NhejcaPH09UVBQ///wznp6ezd0dIRpMPr9aJjk3idrIuenK4TKpayEhIej1erKzs2vcnp2dTVhYmO360KFDycjIYMWKFU3dReFAAQEBdO7cmaNHj9puk2PrGqzv18u9lwFGjhzJ7t27a00bEOJKIOemq4ucm1yXnJtaJpcJdAwGAwkJCaxZs8Z2m8ViYc2aNQwYMMB22+23387nn3/OQw89xJdfftkcXRUOUFxczLFjxwgPD7fdJsfWNbRr146wsLAa7+XCwkI2b95c470M8Pvf/57XXnuN22+/nXXr1jV1V4W4LDk3XV3k3OS65NzUQqku5Msvv1SNRqP6ySefqPv371cffvhhNSAgQM3KylJVVVUB9bvvvlNVVVX/85//qB4eHup//vOfZuyxqK8//elPalJSknr8+HF148aN6pAhQ9SQkBA1JydHVVU5ti1NUVGRunPnTnXnzp0qoM6bN0/duXOneuLECVVVVfW1115TAwIC1B9++EHdvXu3Onr0aLVdu3ZqWVmZqqqqevz4cRVQd+7cqaqqqr711luqj4+PumHDhuZ6SkLUSc5NrkvOTa5Fzk2ux6UCHVVV1bfffltt06aNajAY1H79+qm//fab7b7zP3BUVVWXLFmienh4qN98800z9FTYY9y4cWp4eLhqMBjUyMhIddy4cerRo0dt98uxbVnWrl2rAhddJk2apKqqqlosFnXWrFlqaGioajQa1cGDB6uHDh2yPf7Ck4mqquqbb76p+vr6qhs3bmziZyPE5cm5yTXJucm1yLnJ9SiqqqpNN34khBBCCCGEEM7nMnN0hBBCCCGEEMJKAh0hhBBCCCGEy5FARwghhBBCCOFyJNARQgghhBBCuBwJdIQQQgghhBAuRwIdIYQQQgghhMuRQEcIIYQQQgjhciTQEUIIIYQQQrgcCXSEcJDJkyczZsyY5u6GEEIIAch5SQgJdIQQQgghhBAuRwIdIez09ddfExsbi6enJ8HBwQwZMoQ///nP/Otf/+KHH35AURQURSEpKQmAkydPcs899xAQEEBQUBCjR48mNTXV1p71F7fnn3+eVq1a4efnxyOPPILJZGqeJyiEEKJFkfOSELVza+4OCNGSZGZmMn78eF5//XXGjh1LUVERGzZsYOLEiaSlpVFYWMiiRYsACAoKwmw2M3z4cAYMGMCGDRtwc3PjpZde4pZbbmH37t0YDAYA1qxZg4eHB0lJSaSmpjJlyhSCg4N5+eWXm/PpCiGEuMLJeUmIukmgI4QdMjMzqays5I477qBt27YAxMbGAuDp6UlFRQVhYWG27T/99FMsFgsfffQRiqIAsGjRIgICAkhKSmLYsGEAGAwGFi5ciJeXFz169OCFF17gz3/+My+++CI6nQy8CiGEqJ2cl4Som/ylCmGH+Ph4Bg8eTGxsLHfffTcffvgh+fn5dW6/a9cujh49iq+vLz4+Pvj4+BAUFER5eTnHjh2r0a6Xl5ft+oABAyguLubkyZNOfT5CCCFaNjkvCVE3GdERwg56vZ5Vq1axadMmVq5cydtvv80zzzzD5s2ba92+uLiYhIQEPvvss4vua9WqlbO7K4QQwsXJeUmIukmgI4SdFEXh2muv5dprr2X27Nm0bduW7777DoPBQFVVVY1t+/Tpw5IlS2jdujV+fn51trlr1y7Kysrw9PQE4LfffsPHx4fo6GinPhchhBAtn5yXhKidpK4JYYfNmzfzyiuvsG3bNtLS0vj222/Jzc2lW7duxMTEsHv3bg4dOkReXh5ms5kJEyYQEhLC6NGj2bBhA8ePHycpKYn/+7//Iz093dauyWTiwQcfZP/+/Sxbtow5c+Ywffp0yYMWQghxSXJeEqJuMqIjhB38/PxYv3498+fPp7CwkLZt2/Lmm28yYsQIEhMTSUpKIjExkeLiYtauXcuNN97I+vXrefLJJ7njjjsoKioiMjKSwYMH1/glbfDgwXTq1IkbbriBiooKxo8fz3PPPdd8T1QIIUSLIOclIeqmqKqqNncnhLiaTZ48mYKCAr7//vvm7ooQQggh5yXhMmT8UQghhBBCCOFyJNARQgghhBBCuBxJXRNCCCGEEEK4HBnREUIIIYQQQrgcCXSEEEIIIYQQLkcCHSGEEEIIIYTLkUBHCCGEEEII4XIk0BFCCCGEEEK4HAl0hBBCCCGEEC5HAh0hhBBCCCGEy5FARwghhBBCCOFyJNARQgghhBBCuJz/B1hKEbtWvKlmAAAAAElFTkSuQmCC",
      "text/plain": [
       "<Figure size 1000x500 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "def plot_record_curves(record_dict, sample_step=500):\n",
    "    # .set_index(\"step\") 将 step 列设置为 DataFrame 的索引\n",
    "    train_df = pd.DataFrame(record_dict[\"train\"]).set_index(\"step\").iloc[::sample_step]\n",
    "    val_df = pd.DataFrame(record_dict[\"val\"]).set_index(\"step\")\n",
    "\n",
    "    last_step = train_df.index[-1]  # 最后一步的步数\n",
    "\n",
    "    # print(train_df)\n",
    "    # print(val_df)\n",
    "\n",
    "    # 画图 \n",
    "    fig_num = len(train_df.columns)  # 画两张图,分别是损失和准确率\n",
    "\n",
    "    # plt.subplots：用于创建一个包含多个子图的图形窗口。\n",
    "    # 1：表示子图的行数为 1。\n",
    "    # fig_num：表示子图的列数，即子图的数量。\n",
    "    # figsize=(5 * fig_num, 5)：设置整个图形窗口的大小，宽度为 5 * fig_num，高度为 5。\n",
    "    # fig：返回的图形对象（Figure），用于操作整个图形窗口。\n",
    "    # axs：返回的子图对象（Axes 或 Axes 数组），用于操作每个子图。\n",
    "    fig, axs = plt.subplots(1, fig_num, figsize=(5 * fig_num, 5))\n",
    "    for idx, item in enumerate(train_df.columns):\n",
    "        # train_df.index 是 x 轴数据（通常是 step）。\n",
    "        # train_df[item] 是 y 轴数据（当前指标的值）。\n",
    "        axs[idx].plot(train_df.index, train_df[item], label=\"train:\" + item)\n",
    "        # val_df.index 是 x 轴数据。\n",
    "        # val_df[item] 是 y 轴数据。\n",
    "        axs[idx].plot(val_df.index, val_df[item], label=\"val:\" + item)\n",
    "        axs[idx].grid()  # 显示网格\n",
    "        axs[idx].legend()  # 显示图例\n",
    "        axs[idx].set_xticks(range(0, train_df.index[-1] + 1, 5000))  # 设置x轴刻度\n",
    "        axs[idx].set_xticklabels(map(lambda x: f\"{x // 1000}k\", range(0, last_step + 1, 5000)))  # 设置x轴标签\n",
    "        axs[idx].set_xlabel(\"step\")\n",
    "\n",
    "    plt.show()\n",
    "\n",
    "\n",
    "plot_record_curves(record_dict)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5d9729d15a0d01e8",
   "metadata": {},
   "source": [
    "## 评估"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "3195e10a60c29806",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-21T15:14:12.525645Z",
     "iopub.status.busy": "2025-01-21T15:14:12.525187Z",
     "iopub.status.idle": "2025-01-21T15:14:14.381004Z",
     "shell.execute_reply": "2025-01-21T15:14:14.380563Z",
     "shell.execute_reply.started": "2025-01-21T15:14:12.525614Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Test loss: 0.7577, Test acc: 0.7358\n"
     ]
    }
   ],
   "source": [
    "# 加载最好的模型\n",
    "# torch.load：加载保存的模型权重或整个模型。\n",
    "# \"checkpoints/best.ckpt\"：模型权重文件路径。\n",
    "# weights_only=True：仅加载模型的权重，而不是整个模型（包括结构和参数）。这是 PyTorch 2.1 引入的新特性，用于增强安全性。\n",
    "# map_location=device：将模型加载到当前设备（GPU或CPU）。\n",
    "model.load_state_dict(torch.load(\"checkpoints/07_VGG.ckpt\", weights_only=True, map_location=device))  # 加载最好的模型\n",
    "\n",
    "model.eval()  # 评估模式\n",
    "loss, acc = evaluate(model, eval_loader, loss_fct)\n",
    "print(f\"Test loss: {loss:.4f}, Test acc: {acc:.4f}\")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.14"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
